﻿using System.Collections.Generic;
using System.Reflection;
using System;
using Lypshare.Net.Client.Packets;

namespace Lypshare.Net
{
	/// <summary>
	/// Packet.
	/// </summary>
	/// <exception cref='ArgumentNullException'>
	/// Is thrown when an argument passed to a method is invalid because it is <see langword="null" /> .
	/// </exception>
	/// <exception cref='ArgumentException'>
	/// Is thrown when an argument passed to a method is invalid.
	/// </exception>
    public abstract class Packet
    {
        // --------------------------------------------------------
        // Static Variables
        // --------------------------------------------------------


        /// <summary>
        /// The _ packet types.
        /// </summary>
        private static readonly Dictionary<int, Type> _PacketTypes 
            = new Dictionary<int, Type>();


        // --------------------------------------------------------
        // Static Functions
        // --------------------------------------------------------


        /// <summary>
        /// Initializes the <see cref="Lypshare.Net.Packet"/> class.
        /// </summary>
        static Packet()
        {
            var assemblies = AppDomain.CurrentDomain
                .GetAssemblies();

            foreach (var assembly in assemblies)
            {
                foreach (var type in assembly.GetTypes())
                {
                    // Ignore types, which are not a 
                    // subclass from packet
                    if (!type.IsSubclassOf(typeof(Packet))) 
                        continue;

                    _PacketTypes.Add(GetId(type), type);

                } // foreach (var type in assembly.GetTypes())
            } // foreach (var assembly in assemblies)
        } // Static Constructor


        /// <summary>
        /// Gets the identifier.
        /// </summary>
        /// <returns>
        /// The identifier.
        /// </returns>
        /// <param name='packetType'>
        /// Packet type.
        /// </param>
        /// <exception cref='ArgumentNullException'>
        /// Is thrown when an argument passed to a method is invalid because it is <see langword="null" /> .
        /// </exception>
        /// <exception cref='ArgumentException'>
        /// Is thrown when an argument passed to a method is invalid.
        /// </exception>
        public static int GetId(Type packetType)
        {
            if (packetType == null) 
                throw new ArgumentNullException("packetType");

            if (!typeof(Packet).IsAssignableFrom(packetType))
                throw new ArgumentException("Packet type is " +
                    "not assignable from Packet");

            return packetType.FullName.GetHashCode();

        } // GetId


        /// <summary>
        /// Allocate the specified packetType.
        /// </summary>
        /// <param name='packetType'>
        /// Packet type.
        /// </param>
        /// <typeparam name='T'>
        /// The 1st type parameter.
        /// </typeparam>
        public static T Allocate<T>(int packetType) where T : Packet
        {
            if (!_PacketTypes.ContainsKey(packetType)) return null;
            
            var packet = (T)Activator.CreateInstance(
                _PacketTypes[packetType], null );

            return packet;

        } // Allocate


        /// <summary>
        /// Encodes the packet.
        /// </summary>
        /// <param name='packet'>
        /// Packet.
        /// </param>
        /// <param name='buffer'>
        /// Buffer.
        /// </param>
        /// <exception cref='ArgumentNullException'>
        /// Is thrown when an argument passed to a method is invalid because it is <see langword="null" /> .
        /// </exception>
        public static void EncodePacket(Packet packet, PacketBuffer buffer)
        {
            if (packet == null) throw new ArgumentNullException("packet");
            if (buffer == null) throw new ArgumentNullException("buffer");

            buffer.Write(packet.Type);
            packet.Encode(buffer);

            // Set packet length
            packet.Length = buffer.Length;

        } // EncodePacket


        /// <summary>
        /// Parses the packet.
        /// </summary>
        /// <returns>
        /// The packet.
        /// </returns>
        /// <param name='buffer'>
        /// Buffer.
        /// </param>
        /// <exception cref='ArgumentNullException'>
        /// Is thrown when an argument passed to a method is invalid because it is <see langword="null" /> .
        /// </exception>
        public static Packet ParsePacket(PacketBuffer buffer)
        {
            if (buffer == null)
                throw new ArgumentNullException("buffer");

            var packetType = buffer.ReadInt32();
            var packet = Allocate<Packet>(packetType);

            if (packet == null) 
                packet = new UnknownPacket();

            packet.Length = buffer.Length;
            packet.FromRemote = true;
            packet.Parse(buffer);

            return packet;

        } // ParsePacket


        /// <summary>
        /// Parses the packet.
        /// </summary>
        /// <returns>
        /// The packet.
        /// </returns>
        /// <param name='data'>
        /// Data.
        /// </param>
        /// <exception cref='ArgumentNullException'>
        /// Is thrown when an argument passed to a method is invalid because it is <see langword="null" /> .
        /// </exception>
        public static Packet ParsePacket(byte[] data)
        {
            if (data == null)
                throw new ArgumentNullException("data");

            var buffer = new PacketBuffer(data);
            var packet = ParsePacket(buffer);

            return packet;

        } // ParsePacket


        // --------------------------------------------------------
        // Class Properties
        // --------------------------------------------------------


        /// <summary>
        /// Gets the type.
        /// </summary>
        /// <value>
        /// The type.
        /// </value>
        public int Type
        {
            get { return GetId(GetType()); }

        } // Property: Type


        /// <summary>
        /// Gets or sets the length.
        /// </summary>
        /// <value>
        /// The length.
        /// </value>
        public long Length
        {
            get;
            set;

        } // Property: Length


        /// <summary>
        /// Gets or sets the content.
        /// </summary>
        /// <value>
        /// The content.
        /// </value>
        public Packet Content
        {
            get;
            set;

        } // Property: Content


        /// <summary>
        /// Gets or sets a value indicating whether this <see cref="Lypshare.Net.Packet"/> from remote.
        /// </summary>
        /// <value>
        /// <c>true</c> if from remote; otherwise, <c>false</c>.
        /// </value>
        public bool FromRemote
        {
            get;
            set;

        } // Property: FromRemote


        // --------------------------------------------------------
        // Class Functions
        // --------------------------------------------------------


        /// <summary>
        /// Parse the specified buffer.
        /// </summary>
        /// <param name='buffer'>
        /// Buffer.
        /// </param>
        /// <exception cref='ArgumentNullException'>
        /// Is thrown when an argument passed to a method is invalid because it is <see langword="null" /> .
        /// </exception>
        protected virtual void Parse(PacketBuffer buffer)
        {
            if (buffer == null)
                throw new ArgumentNullException("buffer");

            if (buffer.Length > 0)
                Content = ParsePacket(buffer);

        } // Parse


        /// <summary>
        /// Encode the specified buffer.
        /// </summary>
        /// <param name='buffer'>
        /// Buffer.
        /// </param>
        /// <exception cref='ArgumentNullException'>
        /// Is thrown when an argument passed to a method is invalid because it is <see langword="null" /> .
        /// </exception>
        protected virtual void Encode(PacketBuffer buffer)
        {
            if (buffer == null)
                throw new ArgumentNullException("buffer");

            if (Content != null)
                EncodePacket(Content, buffer);

        } // Encode

    } // class Packet
} // namespace Lypshare.Net